/*
 *  Implementation of Mohammad Hossein Homaei's LEACH simulator, in C.
 *  Joseph Finnegan
 *  joseph.finnegan@cs.nuim.ie
 *  2017
 */

#include "leach.h"

/*
 *	The leach algorithm works by designating k cluster heads from the population, where k is the calculated
 *	optimal number of cluster heads. Cluster heads are chosen randomly from all alive nodes that have not recently
 *	had the role, which is indicated by the factor G. All other nodes then join the closest cluster head to them.
 *	In a real network, this is done by all cluster heads emitting an advertising message, and nodes choose the cluster
 *	head with the message with the highest rssi. In this simulation, they are chosen based on closest physical distance.
 *
 *	Note: nodes don't choose a CH inside this function; the optimal CH is found inside the dissEnergyNonCH function just before
 *	calculation of energy lost by the node during the round.
 */

struct ClusterModel* leach(ClusterModel* clusterM, int roundNo)
{
	int r = roundNo;
	float p = clusterM->p;
	int n = clusterM->nodeA.numNode;

	if(clusterM->numCluster != 0)
	{
		/*	
		 *	Once every r % optimalClusterHeads, reset all the Gs, the factor which prevents
		 * 	nodes that have recently been from being chosen as CHs again.
		 *	(r % optimalClusterHeads) as the idea is that each node in a cluster will take turns at being the CH. 
		 */
		if(r % (int) clusterM->numCluster == 0)
		{	
			for(int i=0;i<n;i++)
			{
				clusterM->nodeA.node[i].G = 0;
			}
		}	
	}
	
	int deadCount = 0;
	for(int i=0; i<clusterM->nodeA.numNode; i++)
	{
		if(!(clusterM->nodeA.node[i].dead))
		{
			if(clusterM->nodeA.node[i].energy <= 0)
			{
				clusterM->nodeA.node[i].type = 'D';
            	clusterM->nodeA.node[i].dead = 1;
            	deadCount++;
			}
			else
			{
				clusterM->nodeA.node[i].type = 'N';
			}	
		}
		else
		{
			deadCount++;
		}
	}

	clusterM->nodeA.numDead = deadCount;

	int countCHs = 0;
	clusterM->clusterN.countCHs = 0;
	srand(time(NULL)); 

	/*
	 *	If all nodes are now dead, a cluster formation cannot be made.
	 *
	 */
	if(clusterM->nodeA.numNode - clusterM->nodeA.numDead == 0){
		clusterM->clusterN.countCHs = 0;

		return clusterM;
	}
	else if(clusterM->numCluster == 0 || clusterM->nodeA.numNode - clusterM->nodeA.numDead == 1){
		/* 
	 	 *	When only one node is left the number of clusters generated by the clusterOptimum algorithm is 0.
	 	 * 	Here, we ensure the final node is designated a cluster head.
	 	 */
		clusterM->numCluster = 1;

		for(int i=0; i<clusterM->nodeA.numNode; i++)
		{
			if(!(clusterM->nodeA.node[i].dead))
			{
				clusterM->nodeA.node[i].type = 'C';

				clusterM->nodeA.node[i].G	= round(1/p)-1;

				clusterM->clusterN.cNodes[countCHs].no = i; 

				clusterM->clusterN.cNodes[countCHs].locX = clusterM->nodeA.node[i].x; 

				clusterM->clusterN.cNodes[countCHs].locY = clusterM->nodeA.node[i].y;

				clusterM->clusterN.cNodes[countCHs].distance = sqrt(pow(clusterM->nodeA.node[i].x - clusterM->netA.sink.x, 2) + pow(clusterM->nodeA.node[i].y - clusterM->netA.sink.y, 2));

				countCHs++;

				if(countCHs == clusterM->numCluster){
					break;
				}
			}
		}
	} 
	else
	{
		/*	
		 *	In cases where there is more than one node remaining, randomly choose cluster heads from the population,
		 *	factoring in G, which prevents nodes that have recently been chosen to be CHs from being chosen again.
		 */
		int stop = 1;
		while(countCHs < clusterM->numCluster && stop){
			for(int i=0; i<clusterM->nodeA.numNode; i++)
			{
				if(!(clusterM->nodeA.node[i].dead))
				{
					int temp_rand = rand(); 

					if(clusterM->nodeA.node[i].G <= 0 && ((temp_rand % (clusterM->nodeA.numNode - clusterM->nodeA.numDead)) <= clusterM->numCluster) && (clusterM->nodeA.node[i].energy > 0) && clusterM->nodeA.node[i].type != 'C')
					{
						clusterM->nodeA.node[i].type = 'C';

						clusterM->nodeA.node[i].G	= round(1/p)-1;

						clusterM->clusterN.cNodes[countCHs].no = i; 

						clusterM->clusterN.cNodes[countCHs].locX = clusterM->nodeA.node[i].x; 

						clusterM->clusterN.cNodes[countCHs].locY = clusterM->nodeA.node[i].y;

						clusterM->clusterN.cNodes[countCHs].distance = sqrt(pow(clusterM->nodeA.node[i].x - clusterM->netA.sink.x, 2) + pow(clusterM->nodeA.node[i].y - clusterM->netA.sink.y, 2));

						countCHs++;

						if(countCHs == clusterM->numCluster){
							stop = 0;
							break;
						}
					}
				}
			}
		}
	}

	clusterM->clusterN.countCHs = countCHs;

	return clusterM;
}